package num

import (
	"fmt"
	"math"
	"math/big"
)

type NumberType int

const (
	INTEGER_TYPE NumberType = iota
	BIG_INTEGER_TYPE
	RATIONAL_TYPE
	REAL_TYPE
	COMPLEX_TYPE
)

// RNumber interface for real-number operations
type RNumber interface {
	Type() NumberType
	String() string
	RADD(RNumber) RNumber
	RSUB(RNumber) RNumber
	RMUL(RNumber) RNumber
	RDIV(RNumber) RNumber
	RNEG() RNumber
}

// Number interface includes complex
type Number interface {
	Type() NumberType
	String() string
	ADD(Number) Number
	SUB(Number) Number
	MUL(Number) Number
	DIV(Number) Number
	NEG() Number
}

// Basic types per spec
type Integer int64
type BigInteger big.Int
type Rational big.Rat
type Real float64

type Complex struct {
	RealPart RNumber
	ImagPart RNumber
}

const (
	maxInt64 = int64(^uint64(0) >> 1)
	minInt64 = -maxInt64 - 1
)

// --- helpers ---
func intToBig(i Integer) *big.Int { return big.NewInt(int64(i)) }

func bigToIntegerIfFits(a *big.Int) (Integer, bool) {
	if a.BitLen() <= 63 {
		return Integer(a.Int64()), true
	}
	return 0, false
}

func bigIntToNumber(a *big.Int) Number {
	if v, ok := bigToIntegerIfFits(a); ok {
		return v
	}
	return BigInteger(*a)
}

func bigIntToRNumber(a *big.Int) RNumber {
	if v, ok := bigToIntegerIfFits(a); ok {
		return v
	}
	return BigInteger(*a)
}

func bigIntToFloat64(a *big.Int) float64 {
	f := new(big.Float).SetInt(a)
	v, _ := f.Float64()
	if math.IsInf(v, 0) {
		return v
	}
	return v
}

func ratToFloat64(r *big.Rat) float64 {
	f := new(big.Float).SetRat(r)
	v, _ := f.Float64()
	return v
}

// promote order: Integer < BigInteger < Rational < Real
func promoteForTwo(a, b RNumber) NumberType {
	if a.Type() == REAL_TYPE || b.Type() == REAL_TYPE {
		return REAL_TYPE
	}
	if a.Type() == RATIONAL_TYPE || b.Type() == RATIONAL_TYPE {
		return RATIONAL_TYPE
	}
	if a.Type() == BIG_INTEGER_TYPE || b.Type() == BIG_INTEGER_TYPE {
		return BIG_INTEGER_TYPE
	}
	return INTEGER_TYPE
}

func promoteForMultiple(nums ...RNumber) NumberType {
	hasBigInt := false
	hasRat := false
	hasReal := false
	for _, n := range nums {
		if n == nil {
			continue
		}
		switch n.Type() {
		case REAL_TYPE:
			hasReal = true
		case RATIONAL_TYPE:
			hasRat = true
		case BIG_INTEGER_TYPE:
			hasBigInt = true
		}
	}
	if hasReal {
		return REAL_TYPE
	}
	if hasRat {
		return RATIONAL_TYPE
	}
	if hasBigInt {
		return BIG_INTEGER_TYPE
	}
	return INTEGER_TYPE
}

// convert RNumber to *big.Int when possible (Integer or BigInteger)
func rnumberToBigInt(r RNumber) *big.Int {
	switch v := r.(type) {
	case Integer:
		return intToBig(v)
	case BigInteger:
		return (*big.Int)(&v)
	default:
		panic(fmt.Sprintf("rnumberToBigInt: unsupported type %T", r))
	}
}

// convert RNumber to *big.Rat (Rational or integer/bigint)
func rnumberToBigRat(r RNumber) *big.Rat {
	switch v := r.(type) {
	case Rational:
		return (*big.Rat)(&v)
	case Integer:
		return new(big.Rat).SetInt(intToBig(v))
	case BigInteger:
		return new(big.Rat).SetInt((*big.Int)(&v))
	default:
		panic(fmt.Sprintf("rnumberToBigRat: unsupported type %T", r))
	}
}

// convert RNumber to float64 (Real)
func rnumberToFloat64(r RNumber) float64 {
	switch v := r.(type) {
	case Real:
		return float64(v)
	case Integer:
		return float64(v)
	case BigInteger:
		return bigIntToFloat64((*big.Int)(&v))
	case Rational:
		return ratToFloat64((*big.Rat)(&v))
	default:
		panic(fmt.Sprintf("rnumberToFloat64: unsupported type %T", r))
	}
}

// RNumber_CMP compares two RNumber values and returns -1 if a<b, 0 if equal, 1 if a>b.
// It prefers exact rational comparison when both sides are non-real; when either
// side is a real it falls back to float64 comparison.
func RNumber_CMP(a, b RNumber) int {
	if a == nil || b == nil {
		panic("RNumber_CMP: nil argument")
	}

	// If both are non-real (Integer/BigInteger/Rational), compare as big.Rat for exactness.
	if a.Type() != REAL_TYPE && b.Type() != REAL_TYPE {
		ra := rnumberToBigRat(a)
		rb := rnumberToBigRat(b)
		return ra.Cmp(rb)
	}

	// Fallback: compare as float64 (when either side is REAL_TYPE)
	fa := rnumberToFloat64(a)
	fb := rnumberToFloat64(b)
	if fa < fb {
		return -1
	}
	if fa > fb {
		return 1
	}
	return 0
}

// ---------------- Complex ----------------
func (c Complex) Type() NumberType { return COMPLEX_TYPE }

func (c Complex) String() string {
	return fmt.Sprintf("(%s)+(%s)i", c.RealPart.String(), c.ImagPart.String())
}

func (c Complex) ADD(o Number) Number {
	if oc, ok := o.(Complex); ok {
		return Complex{RealPart: c.RealPart.RADD(oc.RealPart), ImagPart: c.ImagPart.RADD(oc.ImagPart)}
	}
	if or, ok := o.(RNumber); ok {
		return Complex{RealPart: c.RealPart.RADD(or), ImagPart: c.ImagPart.RADD(Integer(0))}
	}
	panic(fmt.Sprintf("ADD: unsupported Number type %T", o))
}

func (c Complex) SUB(o Number) Number {
	if oc, ok := o.(Complex); ok {
		return Complex{RealPart: c.RealPart.RSUB(oc.RealPart), ImagPart: c.ImagPart.RSUB(oc.ImagPart)}
	}
	if or, ok := o.(RNumber); ok {
		return Complex{RealPart: c.RealPart.RSUB(or), ImagPart: c.ImagPart.RSUB(Integer(0))}
	}
	panic(fmt.Sprintf("SUB: unsupported Number type %T", o))
}

func (c Complex) NEG() Number {
	return Complex{RealPart: c.RealPart.RNEG(), ImagPart: c.ImagPart.RNEG()}
}

func (c Complex) MUL(o Number) Number {
	var ar, ai, br, bi RNumber
	if oc, ok := o.(Complex); ok {
		ar, ai, br, bi = c.RealPart, c.ImagPart, oc.RealPart, oc.ImagPart
	} else if or, ok := o.(RNumber); ok {
		ar, ai, br, bi = c.RealPart, c.ImagPart, or, Integer(0)
	} else {
		panic(fmt.Sprintf("MUL: unsupported Number type %T", o))
	}

	target := promoteForMultiple(ar, ai, br, bi)
	// for multiplication, prefer big-int over int to avoid overflow
	if target == INTEGER_TYPE {
		target = BIG_INTEGER_TYPE
	}

	switch target {
	case REAL_TYPE:
		a := rnumberToFloat64(ar)
		b := rnumberToFloat64(ai)
		c2 := rnumberToFloat64(br)
		d := rnumberToFloat64(bi)
		real := a*c2 - b*d
		imag := a*d + b*c2
		return Complex{RealPart: Real(real), ImagPart: Real(imag)}
	case RATIONAL_TYPE:
		A := rnumberToBigRat(ar)
		B := rnumberToBigRat(ai)
		C := rnumberToBigRat(br)
		D := rnumberToBigRat(bi)
		// real = A*C - B*D
		real := new(big.Rat)
		real.Mul(A, C)
		tmp := new(big.Rat).Mul(B, D)
		real.Sub(real, tmp)
		// imag = A*D + B*C
		imag := new(big.Rat).Mul(A, D)
		tmp2 := new(big.Rat).Mul(B, C)
		imag.Add(imag, tmp2)
		return Complex{RealPart: Rational(*real), ImagPart: Rational(*imag)}
	case BIG_INTEGER_TYPE:
		A := rnumberToBigInt(ar)
		B := rnumberToBigInt(ai)
		C := rnumberToBigInt(br)
		D := rnumberToBigInt(bi)
		// real = A*C - B*D
		real := new(big.Int).Mul(A, C)
		tmp := new(big.Int).Mul(B, D)
		real.Sub(real, tmp)
		// imag = A*D + B*C
		imag := new(big.Int).Mul(A, D)
		tmp2 := new(big.Int).Mul(B, C)
		imag.Add(imag, tmp2)
		return Complex{RealPart: bigIntToRNumber(real), ImagPart: bigIntToRNumber(imag)}
	default:
		panic("MUL: unknown promotion target")
	}
}

func (c Complex) DIV(o Number) Number {
	var ar, ai, br, bi RNumber
	if oc, ok := o.(Complex); ok {
		ar, ai, br, bi = c.RealPart, c.ImagPart, oc.RealPart, oc.ImagPart
	} else if or, ok := o.(RNumber); ok {
		ar, ai, br, bi = c.RealPart, c.ImagPart, or, Integer(0)
	} else {
		panic(fmt.Sprintf("DIV: unsupported Number type %T", o))
	}

	target := promoteForMultiple(ar, ai, br, bi)
	// division may produce non-integers -> ensure at least Rational
	if target == INTEGER_TYPE || target == BIG_INTEGER_TYPE {
		target = RATIONAL_TYPE
	}

	switch target {
	case REAL_TYPE:
		a := rnumberToFloat64(ar)
		b := rnumberToFloat64(ai)
		c2 := rnumberToFloat64(br)
		d := rnumberToFloat64(bi)
		denom := c2*c2 + d*d
		if denom == 0 {
			panic("DIV: divide by zero")
		}
		real := (a*c2 + b*d) / denom
		imag := (b*c2 - a*d) / denom
		return Complex{RealPart: Real(real), ImagPart: Real(imag)}
	case RATIONAL_TYPE:
		A := rnumberToBigRat(ar)
		B := rnumberToBigRat(ai)
		C := rnumberToBigRat(br)
		D := rnumberToBigRat(bi)
		// denom = C*C + D*D
		denom := new(big.Rat).Mul(C, C)
		tmp := new(big.Rat).Mul(D, D)
		denom.Add(denom, tmp)
		if denom.Num().Sign() == 0 {
			panic("DIV: divide by zero")
		}
		// real = (A*C + B*D) / denom
		realNum := new(big.Rat).Mul(A, C)
		t2 := new(big.Rat).Mul(B, D)
		realNum.Add(realNum, t2)
		realNum.Quo(realNum, denom)
		// imag = (B*C - A*D) / denom
		imagNum := new(big.Rat).Mul(B, C)
		t3 := new(big.Rat).Mul(A, D)
		imagNum.Sub(imagNum, t3)
		imagNum.Quo(imagNum, denom)
		return Complex{RealPart: Rational(*realNum), ImagPart: Rational(*imagNum)}
	default:
		panic("DIV: unknown promotion target")
	}
}

// ---------------- Real ----------------
func (r Real) Type() NumberType { return REAL_TYPE }

func (r Real) String() string { return fmt.Sprintf("%g", float64(r)) }

func (r Real) RADD(o RNumber) RNumber {
	switch v := o.(type) {
	case Real:
		return Real(float64(r) + float64(v))
	case Integer:
		return Real(float64(r) + float64(v))
	case BigInteger:
		return Real(float64(r) + bigIntToFloat64((*big.Int)(&v)))
	case Rational:
		return Real(float64(r) + ratToFloat64((*big.Rat)(&v)))
	default:
		panic(fmt.Sprintf("RADD: unsupported RNumber type %T", o))
	}
}

func (r Real) RSUB(o RNumber) RNumber {
	switch v := o.(type) {
	case Real:
		return Real(float64(r) - float64(v))
	case Integer:
		return Real(float64(r) - float64(v))
	case BigInteger:
		return Real(float64(r) - bigIntToFloat64((*big.Int)(&v)))
	case Rational:
		return Real(float64(r) - ratToFloat64((*big.Rat)(&v)))
	default:
		panic(fmt.Sprintf("RSUB: unsupported RNumber type %T", o))
	}
}

func (r Real) RMUL(o RNumber) RNumber {
	switch v := o.(type) {
	case Real:
		return Real(float64(r) * float64(v))
	case Integer:
		return Real(float64(r) * float64(v))
	case BigInteger:
		return Real(float64(r) * bigIntToFloat64((*big.Int)(&v)))
	case Rational:
		return Real(float64(r) * ratToFloat64((*big.Rat)(&v)))
	default:
		panic(fmt.Sprintf("RMUL: unsupported RNumber type %T", o))
	}
}

func (r Real) RDIV(o RNumber) RNumber {
	switch v := o.(type) {
	case Real:
		if float64(v) == 0 {
			panic("RDIV: divide by zero")
		}
		return Real(float64(r) / float64(v))
	case Integer:
		if v == 0 {
			panic("RDIV: divide by zero")
		}
		return Real(float64(r) / float64(v))
	case BigInteger:
		dv := bigIntToFloat64((*big.Int)(&v))
		if dv == 0 {
			panic("RDIV: divide by zero")
		}
		return Real(float64(r) / dv)
	case Rational:
		dv := ratToFloat64((*big.Rat)(&v))
		if dv == 0 {
			panic("RDIV: divide by zero")
		}
		return Real(float64(r) / dv)
	default:
		panic(fmt.Sprintf("RDIV: unsupported RNumber type %T", o))
	}
}

func (r Real) RNEG() RNumber { return Real(-float64(r)) }

// Number methods for Real
func (r Real) ADD(o Number) Number {
	if oc, ok := o.(Complex); ok {
		return Complex{RealPart: oc.RealPart.RADD(r), ImagPart: oc.ImagPart.RADD(Real(0))}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(r.RADD(or))
	}
	panic(fmt.Sprintf("ADD: unsupported Number type %T", o))
}

func (r Real) SUB(o Number) Number {
	if oc, ok := o.(Complex); ok {
		real := r.RSUB(oc.RealPart)
		imag := oc.ImagPart.RNEG()
		return Complex{RealPart: rnumberToNumber(real).(RNumber), ImagPart: imag}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(r.RSUB(or))
	}
	panic(fmt.Sprintf("SUB: unsupported Number type %T", o))
}

func (r Real) MUL(o Number) Number {
	if oc, ok := o.(Complex); ok {
		real := r.RMUL(oc.RealPart)
		imag := r.RMUL(oc.ImagPart)
		return Complex{RealPart: rnumberToNumber(real).(RNumber), ImagPart: imag}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(r.RMUL(or))
	}
	panic(fmt.Sprintf("MUL: unsupported Number type %T", o))
}

func (r Real) DIV(o Number) Number {
	if oc, ok := o.(Complex); ok {
		return Complex{RealPart: r, ImagPart: Real(0)}.DIV(oc)
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(r.RDIV(or))
	}
	panic(fmt.Sprintf("DIV: unsupported Number type %T", o))
}

func (r Real) NEG() Number { return rnumberToNumber(r.RNEG()) }

func rnumberToNumber(r RNumber) Number {
	switch v := r.(type) {
	case Integer:
		return v
	case BigInteger:
		return v
	case Rational:
		return v
	case Real:
		return v
	default:
		// if it already implements Number
		if n, ok := any(r).(Number); ok {
			return n
		}
		panic(fmt.Sprintf("rnumberToNumber: cannot convert %T to Number", r))
	}
}

// ---------------- BigInteger ----------------
func (b BigInteger) Type() NumberType { return BIG_INTEGER_TYPE }

func (b BigInteger) String() string { return (*big.Int)(&b).String() }

func (b BigInteger) RADD(o RNumber) RNumber {
	a := new(big.Int)
	a.Set((*big.Int)(&b))
	switch v := o.(type) {
	case BigInteger:
		a.Add(a, (*big.Int)(&v))
	case Integer:
		a.Add(a, intToBig(v))
	case Rational:
		// promote to Rational
		ar := new(big.Rat).SetInt(a)
		ar.Add(ar, (*big.Rat)(&v))
		return Rational(*ar)
	case Real:
		fv := bigIntToFloat64(a)
		return Real(fv + float64(v))
	default:
		panic(fmt.Sprintf("RADD: unsupported RNumber type %T", o))
	}
	return bigIntToRNumber(a)
}

func (b BigInteger) RSUB(o RNumber) RNumber {
	a := new(big.Int)
	a.Set((*big.Int)(&b))
	switch v := o.(type) {
	case BigInteger:
		a.Sub(a, (*big.Int)(&v))
	case Integer:
		a.Sub(a, intToBig(v))
	case Rational:
		ar := new(big.Rat).SetInt(a)
		ar.Sub(ar, (*big.Rat)(&v))
		return Rational(*ar)
	case Real:
		fv := bigIntToFloat64(a)
		return Real(fv - float64(v))
	default:
		panic(fmt.Sprintf("RSUB: unsupported RNumber type %T", o))
	}
	return bigIntToRNumber(a)
}

func (b BigInteger) RMUL(o RNumber) RNumber {
	a := new(big.Int)
	a.Set((*big.Int)(&b))
	switch v := o.(type) {
	case BigInteger:
		a.Mul(a, (*big.Int)(&v))
	case Integer:
		a.Mul(a, intToBig(v))
	case Rational:
		ar := new(big.Rat).SetInt(a)
		ar.Mul(ar, (*big.Rat)(&v))
		return Rational(*ar)
	case Real:
		fv := bigIntToFloat64(a)
		return Real(fv * float64(v))
	default:
		panic(fmt.Sprintf("RMUL: unsupported RNumber type %T", o))
	}
	return bigIntToRNumber(a)
}

func (b BigInteger) RDIV(o RNumber) RNumber {
	a := new(big.Int)
	a.Set((*big.Int)(&b))
	switch v := o.(type) {
	case BigInteger:
		denom := (*big.Int)(&v)
		if denom.Sign() == 0 {
			panic("RDIV: divide by zero")
		}
		a.Div(a, denom)
		return bigIntToRNumber(a)
	case Integer:
		if v == 0 {
			panic("RDIV: divide by zero")
		}
		a.Div(a, intToBig(v))
		return bigIntToRNumber(a)
	case Rational:
		ar := new(big.Rat).SetInt(a)
		ar.Quo(ar, (*big.Rat)(&v))
		return Rational(*ar)
	case Real:
		fv := bigIntToFloat64(a)
		if float64(v) == 0 {
			panic("RDIV: divide by zero")
		}
		return Real(fv / float64(v))
	default:
		panic(fmt.Sprintf("RDIV: unsupported RNumber type %T", o))
	}
}

func (b BigInteger) RNEG() RNumber {
	a := new(big.Int)
	a.Neg((*big.Int)(&b))
	return bigIntToRNumber(a)
}

// Number methods for BigInteger
func (b BigInteger) ADD(o Number) Number {
	if oc, ok := o.(Complex); ok {
		return Complex{RealPart: oc.RealPart.RADD(b), ImagPart: oc.ImagPart.RADD(Integer(0))}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(b.RADD(or))
	}
	panic(fmt.Sprintf("ADD: unsupported Number type %T", o))
}

func (b BigInteger) SUB(o Number) Number {
	if oc, ok := o.(Complex); ok {
		real := b.RSUB(oc.RealPart)
		imag := oc.ImagPart.RNEG()
		return Complex{RealPart: rnumberToNumber(real).(RNumber), ImagPart: imag}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(b.RSUB(or))
	}
	panic(fmt.Sprintf("SUB: unsupported Number type %T", o))
}

func (b BigInteger) MUL(o Number) Number {
	if oc, ok := o.(Complex); ok {
		real := b.RMUL(oc.RealPart)
		imag := b.RMUL(oc.ImagPart)
		return Complex{RealPart: rnumberToNumber(real).(RNumber), ImagPart: imag}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(b.RMUL(or))
	}
	panic(fmt.Sprintf("MUL: unsupported Number type %T", o))
}

func (b BigInteger) DIV(o Number) Number {
	if oc, ok := o.(Complex); ok {
		return Complex{RealPart: b, ImagPart: Integer(0)}.DIV(oc)
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(b.RDIV(or))
	}
	panic(fmt.Sprintf("DIV: unsupported Number type %T", o))
}

func (b BigInteger) NEG() Number { return rnumberToNumber(b.RNEG()) }

// ---------------- Rational ----------------
func (r Rational) Type() NumberType { return RATIONAL_TYPE }

func (r Rational) String() string { return (*big.Rat)(&r).String() }

func (r Rational) RADD(o RNumber) RNumber {
	ar := new(big.Rat)
	ar.Set((*big.Rat)(&r))
	switch v := o.(type) {
	case Rational:
		ar.Add(ar, (*big.Rat)(&v))
	case Integer:
		ar.Add(ar, new(big.Rat).SetInt(intToBig(v)))
	case BigInteger:
		ar.Add(ar, new(big.Rat).SetInt((*big.Int)(&v)))
	case Real:
		return Real(ratToFloat64(ar) + float64(v))
	default:
		panic(fmt.Sprintf("RADD: unsupported RNumber type %T", o))
	}
	return Rational(*ar)
}

func (r Rational) RSUB(o RNumber) RNumber {
	ar := new(big.Rat)
	ar.Set((*big.Rat)(&r))
	switch v := o.(type) {
	case Rational:
		ar.Sub(ar, (*big.Rat)(&v))
	case Integer:
		ar.Sub(ar, new(big.Rat).SetInt(intToBig(v)))
	case BigInteger:
		ar.Sub(ar, new(big.Rat).SetInt((*big.Int)(&v)))
	case Real:
		return Real(ratToFloat64(ar) - float64(v))
	default:
		panic(fmt.Sprintf("RSUB: unsupported RNumber type %T", o))
	}
	return Rational(*ar)
}

func (r Rational) RMUL(o RNumber) RNumber {
	ar := new(big.Rat)
	ar.Set((*big.Rat)(&r))
	switch v := o.(type) {
	case Rational:
		ar.Mul(ar, (*big.Rat)(&v))
	case Integer:
		ar.Mul(ar, new(big.Rat).SetInt(intToBig(v)))
	case BigInteger:
		ar.Mul(ar, new(big.Rat).SetInt((*big.Int)(&v)))
	case Real:
		return Real(ratToFloat64(ar) * float64(v))
	default:
		panic(fmt.Sprintf("RMUL: unsupported RNumber type %T", o))
	}
	return Rational(*ar)
}

func (r Rational) RDIV(o RNumber) RNumber {
	ar := new(big.Rat)
	ar.Set((*big.Rat)(&r))
	switch v := o.(type) {
	case Rational:
		if (*big.Rat)(&v).Num().Sign() == 0 {
			panic("RDIV: divide by zero")
		}
		ar.Quo(ar, (*big.Rat)(&v))
	case Integer:
		if v == 0 {
			panic("RDIV: divide by zero")
		}
		ar.Quo(ar, new(big.Rat).SetInt(intToBig(v)))
	case BigInteger:
		if (*big.Int)(&v).Sign() == 0 {
			panic("RDIV: divide by zero")
		}
		ar.Quo(ar, new(big.Rat).SetInt((*big.Int)(&v)))
	case Real:
		if float64(v) == 0 {
			panic("RDIV: divide by zero")
		}
		return Real(ratToFloat64(ar) / float64(v))
	default:
		panic(fmt.Sprintf("RDIV: unsupported RNumber type %T", o))
	}
	return Rational(*ar)
}

func (r Rational) RNEG() RNumber {
	a := new(big.Rat)
	a.Neg((*big.Rat)(&r))
	return Rational(*a)
}

// Number methods for Rational
func (r Rational) ADD(o Number) Number {
	if oc, ok := o.(Complex); ok {
		return Complex{RealPart: oc.RealPart.RADD(r), ImagPart: oc.ImagPart.RADD(Integer(0))}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(r.RADD(or))
	}
	panic(fmt.Sprintf("ADD: unsupported Number type %T", o))
}

func (r Rational) SUB(o Number) Number {
	if oc, ok := o.(Complex); ok {
		real := r.RSUB(oc.RealPart)
		imag := oc.ImagPart.RNEG()
		return Complex{RealPart: rnumberToNumber(real).(RNumber), ImagPart: imag}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(r.RSUB(or))
	}
	panic(fmt.Sprintf("SUB: unsupported Number type %T", o))
}

func (r Rational) MUL(o Number) Number {
	if oc, ok := o.(Complex); ok {
		real := r.RMUL(oc.RealPart)
		imag := r.RMUL(oc.ImagPart)
		return Complex{RealPart: rnumberToNumber(real).(RNumber), ImagPart: imag}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(r.RMUL(or))
	}
	panic(fmt.Sprintf("MUL: unsupported Number type %T", o))
}

func (r Rational) DIV(o Number) Number {
	if oc, ok := o.(Complex); ok {
		return Complex{RealPart: r, ImagPart: Rational(*new(big.Rat))}.DIV(oc)
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(r.RDIV(or))
	}
	panic(fmt.Sprintf("DIV: unsupported Number type %T", o))
}

func (r Rational) NEG() Number { return rnumberToNumber(r.RNEG()) }

// ---------------- Integer ----------------
func (i Integer) Type() NumberType { return INTEGER_TYPE }

func (i Integer) String() string { return fmt.Sprintf("%d", int64(i)) }

func (i Integer) RADD(o RNumber) RNumber {
	switch v := o.(type) {
	case Integer:
		ai := int64(i)
		bi := int64(v)
		if (bi > 0 && ai > maxInt64-bi) || (bi < 0 && ai < minInt64-bi) {
			a := intToBig(i)
			a.Add(a, intToBig(v))
			return bigIntToRNumber(a)
		}
		return Integer(ai + bi)
	case BigInteger:
		a := intToBig(i)
		a.Add(a, (*big.Int)(&v))
		return bigIntToRNumber(a)
	case Rational:
		// convert Integer to Rat
		ar := new(big.Rat).SetInt(intToBig(i))
		ar.Add(ar, (*big.Rat)(&v))
		return Rational(*ar)
	case Real:
		return Real(float64(i) + float64(v))
	default:
		panic(fmt.Sprintf("RADD: unsupported RNumber type %T", o))
	}
}

func (i Integer) RSUB(o RNumber) RNumber {
	switch v := o.(type) {
	case Integer:
		ai := int64(i)
		bi := int64(v)
		if (bi < 0 && ai > maxInt64+bi) || (bi > 0 && ai < minInt64+bi) {
			a := intToBig(i)
			a.Sub(a, intToBig(v))
			return bigIntToRNumber(a)
		}
		return Integer(ai - bi)
	case BigInteger:
		a := intToBig(i)
		a.Sub(a, (*big.Int)(&v))
		return bigIntToRNumber(a)
	case Rational:
		ar := new(big.Rat).SetInt(intToBig(i))
		ar.Sub(ar, (*big.Rat)(&v))
		return Rational(*ar)
	case Real:
		return Real(float64(i) - float64(v))
	default:
		panic(fmt.Sprintf("RSUB: unsupported RNumber type %T", o))
	}
}

func (i Integer) RMUL(o RNumber) RNumber {
	switch v := o.(type) {
	case Integer:
		ai := int64(i)
		bi := int64(v)
		prod := ai * bi
		if ai != 0 && prod/ai != bi {
			a := intToBig(i)
			a.Mul(a, intToBig(v))
			return bigIntToRNumber(a)
		}
		return Integer(prod)
	case BigInteger:
		a := intToBig(i)
		a.Mul(a, (*big.Int)(&v))
		return bigIntToRNumber(a)
	case Rational:
		ar := new(big.Rat).SetInt(intToBig(i))
		ar.Mul(ar, (*big.Rat)(&v))
		return Rational(*ar)
	case Real:
		return Real(float64(i) * float64(v))
	default:
		panic(fmt.Sprintf("RMUL: unsupported RNumber type %T", o))
	}
}

func (i Integer) RDIV(o RNumber) RNumber {
	switch v := o.(type) {
	case Integer:
		if v == 0 {
			panic("RDIV: divide by zero")
		}
		return Integer(int64(i) / int64(v))
	case BigInteger:
		denom := (*big.Int)(&v)
		if denom.Sign() == 0 {
			panic("RDIV: divide by zero")
		}
		a := intToBig(i)
		a.Div(a, denom)
		return bigIntToRNumber(a)
	case Rational:
		ar := new(big.Rat).SetInt(intToBig(i))
		ar.Quo(ar, (*big.Rat)(&v))
		return Rational(*ar)
	case Real:
		if float64(v) == 0 {
			panic("RDIV: divide by zero")
		}
		return Real(float64(i) / float64(v))
	default:
		panic(fmt.Sprintf("RDIV: unsupported RNumber type %T", o))
	}
}

func (i Integer) RNEG() RNumber { return Integer(-int64(i)) }

// Number methods for Integer
func (i Integer) ADD(o Number) Number {
	if oc, ok := o.(Complex); ok {
		return Complex{RealPart: oc.RealPart.RADD(i), ImagPart: oc.ImagPart.RADD(Integer(0))}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(i.RADD(or))
	}
	panic(fmt.Sprintf("ADD: unsupported Number type %T", o))
}

func (i Integer) SUB(o Number) Number {
	if oc, ok := o.(Complex); ok {
		// i - (cr + ci i) => (i - cr) + (-ci)i
		real := i.RSUB(oc.RealPart)
		imag := oc.ImagPart.RNEG()
		return Complex{RealPart: rnumberToNumber(real).(RNumber), ImagPart: imag}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(i.RSUB(or))
	}
	panic(fmt.Sprintf("SUB: unsupported Number type %T", o))
}

func (i Integer) MUL(o Number) Number {
	if oc, ok := o.(Complex); ok {
		// (a)*(c+di) = (a*c) + (a*d)i
		real := i.RMUL(oc.RealPart)
		imag := i.RMUL(oc.ImagPart)
		return Complex{RealPart: rnumberToNumber(real).(RNumber), ImagPart: imag}
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(i.RMUL(or))
	}
	panic(fmt.Sprintf("MUL: unsupported Number type %T", o))
}

func (i Integer) DIV(o Number) Number {
	if oc, ok := o.(Complex); ok {
		return Complex{RealPart: i, ImagPart: Integer(0)}.DIV(oc)
	}
	if or, ok := o.(RNumber); ok {
		return rnumberToNumber(i.RDIV(or))
	}
	panic(fmt.Sprintf("DIV: unsupported Number type %T", o))
}

func (i Integer) NEG() Number { return Integer(-int64(i)) }

func NewRational(numerator int64, denominator int64) Number {
	r := new(big.Rat).SetFrac(big.NewInt(numerator), big.NewInt(denominator))
	return Rational(*r)
}
